package com.webcams.ui.compass;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;

import com.webcams.core.location.LocationManagerListener;
import com.webcams.core.location.ResortLocationManager;
import com.webcams.core.resorts.Resort;
import com.webcams.core.resorts.ResortsManager;
import com.webcams.core.utils.Utils;
import com.webcams.ui.R;

/*
 * Chamrousse 45.1247	5.87819
 * Les 7 Laux 45.2569	5.99588
 */
public class CompassView extends View implements LocationManagerListener, SensorEventListener {

	public final static int PORTRAIT = 0;
	public final static int LANDSCAPE = 1;
	public static final int MODE_COMPASS = 0;
	public static final int MODE_AUGMENTEDREALITY = 1;

	private float northDegree;
	private Display display;

	private HashMap<Resort, Double> cacheAngle;
	private HashMap<Resort, Double> cacheDist;
	private int virtualRadius;
	private CompassUI context;
	private int currentMode;
	private float horizontalViewAngle;
	private float verticalViewAngle;

	private SensorManager sensorManager;
	private float roll;
	private float pitch;

	private float[] mMagneticValues;
	private float[] mAccelerometerValues;

	public CompassView(CompassUI context, int defaultMode) {
		super(context);

		this.currentMode = defaultMode;

		northDegree = 0;
		virtualRadius = 10;
		display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

		cacheAngle = new HashMap<Resort, Double>();
		cacheDist = new HashMap<Resort, Double>();

		this.context = context;

		ResortLocationManager.getInstance(context).addListener(this);

		final ProgressDialog progressDialog = new ProgressDialog(context);
		progressDialog.setCancelable(false);
		progressDialog.setMessage(getResources().getString(R.string.compass_loading));
		progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		progressDialog.show();


		sensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
		new Thread(new Runnable(){

			@Override
			public void run() {
				sensorManager.registerListener(CompassView.this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
				sensorManager.registerListener(CompassView.this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_NORMAL);
				progressDialog.dismiss();
			}

		}).start();
	}

	public void resizeRadius(boolean isPlus) {

		int tempNum = (int) (virtualRadius / Math.pow(10, (int)Math.log10(virtualRadius)));
		float multiplicator = (float) (!isPlus ? ((tempNum == 2) ? 5.0/2 : 2) : ((tempNum == 5) ? 5.0/2 : 2));

		virtualRadius = (int) (!isPlus ? virtualRadius*multiplicator : virtualRadius/multiplicator);

		if(virtualRadius < 5) {
			virtualRadius = 5;
		} else if(virtualRadius > 5000) {
			virtualRadius = 5000;
		}
		invalidate();
	}

	public int getScreenOrientation() {
		return getWidth() > getHeight() ? LANDSCAPE : PORTRAIT;	
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {

		if(currentMode == MODE_COMPASS) {

			DashPathEffect dashPath = new DashPathEffect(new float[]{3, 3}, 1);
			float widthScreen = this.getWidth();
			float heightScreen = this.getHeight();
			int radiusUI = (int) (Math.min(widthScreen, heightScreen)/2);

			int circleNumbers = 4;

			for(int i = 1; i < circleNumbers+1; i++){
				Paint circle = new Paint();
				circle.setStrokeWidth(3);
				circle.setStyle(Paint.Style.STROKE);
				circle.setColor(Color.WHITE);
				if(i != circleNumbers) {
					circle.setPathEffect(dashPath);
				}
				canvas.drawCircle(widthScreen/2, heightScreen/2, radiusUI * i/circleNumbers, circle);
			}

			Paint smallCircle = new Paint();
			smallCircle.setStrokeWidth(3);
			smallCircle.setStyle(Paint.Style.FILL);
			smallCircle.setColor(Color.WHITE);
			canvas.drawCircle(widthScreen/2, heightScreen/2, 5, smallCircle);

			Paint text = new Paint();
			int textSize = 24;
			text.setColor(Color.WHITE);
			text.setTextSize(textSize);

			float textX, textY;
			if(getScreenOrientation() == LANDSCAPE) {
				textX = (widthScreen/2) + radiusUI + 5;
				textY = (heightScreen/2) + textSize/2;
			} else {
				textX = (widthScreen/2) - 70;			
				textY = (heightScreen/2) + radiusUI + textSize + 5;
			}
			canvas.drawText("Radius : "+virtualRadius+" km", textX, textY, text);
		}
		super.dispatchDraw(canvas);

	}

	@Override
	protected void onDraw(Canvas canvas){

		float widthScreen = this.getWidth();
		float heightScreen = this.getHeight();

		if(currentMode == MODE_COMPASS) {

			int radiusUI = (int) (Math.min(widthScreen, heightScreen)/2);

			switch(display.getRotation()) {

			case Surface.ROTATION_90:
				northDegree += 90;
				break;
			case Surface.ROTATION_270:
				northDegree -= 90;
				break;
			}


			float NorthX = (float) (widthScreen/2 - radiusUI * Math.sin(Math.toRadians(northDegree)));
			float NorthY = (float) (heightScreen/2 - radiusUI * Math.cos(Math.toRadians(northDegree)));
			Paint resortNorth = new Paint();
			resortNorth.setColor(Color.WHITE);
			resortNorth.setStrokeWidth(3);
			canvas.drawLine(NorthX, NorthY, widthScreen/2, heightScreen/2, resortNorth);

			Bitmap house = BitmapFactory.decodeResource(getResources(), R.drawable.compass_house_small);
			int resortNameSize = 18;
			Paint resortText = new Paint();
			resortText.setColor(Color.WHITE);
			resortText.setTextSize(resortNameSize);


			for(Resort r : ResortsManager.getInstance(context)) {
				String resortName = r.getName();

				if(!cacheDist.containsKey(r) || !cacheAngle.containsKey(r)) {
					continue;
				}

				double resortRadius = cacheDist.get(r)/virtualRadius;
				double resortAngle = northDegree - cacheAngle.get(r);

				if(resortRadius > 1) {
					resortRadius = 1;
				}

				float resortTextWidth = resortText.measureText(resortName);

				float resortX = (float) (widthScreen/2 - resortRadius * radiusUI * Math.sin(Math.toRadians(resortAngle)));
				float resortY = (float) (heightScreen/2 - resortRadius * radiusUI * Math.cos(Math.toRadians(resortAngle)));
				canvas.drawBitmap(house, resortX - house.getWidth()/2, resortY - house.getHeight()/2, null);
				canvas.drawText(resortName, resortX - resortTextWidth/2, resortY - house.getHeight()/2 - 5, resortText);		
			}
		} else if(currentMode == MODE_AUGMENTEDREALITY) {

			float tempNorthDegree = northDegree + 90;
			float firstAngle = Utils.positiveModulo((int) (tempNorthDegree - horizontalViewAngle/2), 360);

			Bitmap house = BitmapFactory.decodeResource(getResources(), R.drawable.compass_house);
			int resortNameSize = 32;

			
			Paint resortBg = new Paint();
			resortBg.setColor(Color.WHITE);
			resortBg.setStyle(Paint.Style.FILL);

			DashPathEffect dashPath = new DashPathEffect(new float[]{3, 3}, 1);
			Paint resortBgBorder = new Paint();
			resortBgBorder.setStrokeWidth(3);
			resortBgBorder.setStyle(Paint.Style.STROKE);
			resortBgBorder.setColor(Color.BLACK);
			resortBgBorder.setPathEffect(dashPath);
			
			double distVerticalAngle = angleToPixels(90 + verticalViewAngle/2, heightScreen, -roll, verticalViewAngle);

			ResortsManager resorts = (ResortsManager) ResortsManager.getInstance(context).clone();
			Collections.sort(resorts, new DistComparator());
			Collections.reverse(resorts);
			
			for(Resort r : resorts) {

				if(!cacheDist.containsKey(r) || !cacheAngle.containsKey(r)) {
					continue;
				}
				double distHorizontalAngle = angleToPixels(cacheAngle.get(r), widthScreen, firstAngle, horizontalViewAngle);

				if(distHorizontalAngle > widthScreen+300 || distHorizontalAngle < 0) {
					continue;
				}

				String resortName = r.getName()+" ("+Utils.round(cacheDist.get(r),1)+"km)";

				if(distHorizontalAngle < widthScreen/2) {
					resortNameSize = (int) (12 + distHorizontalAngle*40/widthScreen);
				} else {
					resortNameSize = (int) (52 - distHorizontalAngle*40/widthScreen);
				}
				
				float scale =  resortNameSize / 32f;
				
				if(scale < .1f) {
					scale = .1f;
				}
				
				Log.v("DEBUG","!!>> "+resortName+" => "+scale+" resortNameSize: "+resortNameSize);
				
                Bitmap houseResized = Bitmap.createScaledBitmap(house, (int)(house.getWidth()*scale), 
                		(int) (house.getHeight()*scale), false);                   
				
				Paint resortText = new Paint();
				resortText.setColor(Color.BLACK);
				resortText.setTextSize(resortNameSize);
				float resortTextWidth = resortText.measureText(resortName);

				float resortX = (float) distHorizontalAngle;
				float resortY = (float) (heightScreen - distVerticalAngle);
				
				float resortTextX = resortX - resortTextWidth/2;
				float resortTextY = resortY - houseResized.getHeight()/2 - 5;
				
				int padding = 10;
				canvas.drawRect(new Rect((int)resortTextX - padding, 
						(int) (resortTextY - resortNameSize - padding),
						(int)(resortTextX + resortTextWidth + padding), 
						(int)resortTextY + padding), resortBg);
				
				canvas.drawRect(new Rect((int)resortTextX - padding, 
						(int) (resortTextY - resortNameSize - padding),
						(int)(resortTextX + resortTextWidth + padding), 
						(int)resortTextY + padding), resortBgBorder);

				canvas.drawText(resortName, resortTextX, resortTextY, resortText);
				
				canvas.drawBitmap(houseResized, resortX - houseResized.getWidth()/2, resortY - houseResized.getHeight()/2, null);

			}
		}
	}

	public double angleToPixels(double angle, float widthScreen, float firstAngle, float viewAngle) {
		int tempAngle = Utils.positiveModulo((int) angle, 360);
		return widthScreen * (Utils.positiveModulo((int) (tempAngle - firstAngle),360) / viewAngle);
	}


	protected void orientationChanged(double azimuth, double pitch, double roll) {
		if(Math.abs(azimuth-northDegree) > 5 ||
				Math.abs(roll-this.roll) > 5 ||
				Math.abs(pitch-this.pitch) > 5) {
			this.roll = (float) roll;
			this.pitch = (float) pitch;
			this.northDegree = (float) azimuth;
			this.invalidate();
		}
	}


	@Override
	public void onLocationChanged(Location l) {		
		if(l == null) {
			cacheAngle.clear();
			cacheDist.clear();
			context.setNoGps();
			return;
		} else {
			context.setGPSFine(l.hasAccuracy() ? l.getAccuracy() : -1);
		}

		for(Resort r : ResortsManager.getInstance(context)) {
			double resortRadius = r.getDistanceFrom(l.getLatitude(), l.getLongitude());
			double resortAngle = r.getAngleFrom(l.getLatitude(), l.getLongitude());

			cacheAngle.put(r, resortAngle);
			cacheDist.put(r, resortRadius);
		}

		this.invalidate();
	}

	public void setMode(int mode) {
		this.currentMode = mode;
	}

	public void setCameraParameters(float horizontalViewAngle, float verticalViewAngle) {
		this.verticalViewAngle = verticalViewAngle;
		this.horizontalViewAngle = horizontalViewAngle;
	}

	public void removeListeners() {
		sensorManager.unregisterListener(this);		
	}

	public void onSensorChanged(SensorEvent event) {
		synchronized (this) {
			switch (event.sensor.getType()) {
			case Sensor.TYPE_MAGNETIC_FIELD:
				mMagneticValues = event.values.clone();
				break;
			case Sensor.TYPE_ACCELEROMETER:
				mAccelerometerValues = event.values.clone();
				break;
			}

			if (mMagneticValues != null && mAccelerometerValues != null) {
				float[] R = new float[16];
				SensorManager.getRotationMatrix(R, null, mAccelerometerValues, mMagneticValues);
				float[] orientation = new float[3];
				SensorManager.getOrientation(R, orientation);

				Log.v("DEBUG","!!>x:"+Float.toString(orientation[0]));
				Log.v("DEBUG","!!>y:"+Float.toString(orientation[1]));
				Log.v("DEBUG","!!>z-:"+Float.toString(orientation[2]));

				orientationChanged(Math.toDegrees(orientation[0]), 
						Math.toDegrees(orientation[1]), 
						Math.toDegrees(orientation[2]));
			}
		}

	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}		

	public class DistComparator implements Comparator<Resort> {
		@Override
		public int compare(Resort r1, Resort r2) {
			if(!cacheDist.containsKey(r1) || !cacheDist.containsKey(r2)) {
				return 1;
			}
			return cacheDist.get(r1).compareTo(cacheDist.get(r2));
		}
	}
	
}
